package com.hyzh.latte.vehicle.nezha;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.coffee.concurrent.CoffeeExecutorManager;
import com.hyzh.latte.vehicle.dmo.*;
import com.hyzh.latte.vehicle.dmo.entity.CameraPGEntity;
import com.hyzh.latte.vehicle.dmo.entity.GatePGEntity;
import com.hyzh.latte.vehicle.dmo.entity.VehicleTrackRecordPGEntity;
import com.hyzh.latte.vehicle.service.MinioService;
import com.hyzh.latte.vehicle.service.NezhaRecordService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 车辆轨迹计算
 * @author songkui
 * @since 2024/3/21 15:25
 */
@Component
@Slf4j
@AllArgsConstructor
public class VehicleTrackHandler {

    private final NezhaRecordService nezhaRecordService;
    private final MinioService minioService;

    public void track(String park, VehicleTrackDTO trackDTO){
        log.info("handler>>  park:{},  {}", park, trackDTO);

        int doType = trackDTO.getDoType();
        switch (doType){
            case 0:
                doCameraTrack(park, trackDTO);
                break;

            case 2:
                doSwerve(park, trackDTO);
                break;

            case 8:
                saveTrack(park, trackDTO);
                doEnterCompany(park, trackDTO);
                break;

            case 9:
                doGate(park, trackDTO);
                break;
            default:
                log.info("handler>> doType {} is invalid", doType);
        }

    }

    public void doInOutEnterprises(String park, VehicleInoutEnterpriseDTO data){

        String path = null;
        try {
            String base64 = data.getBaseImage();
            if (StrUtil.isNotEmpty(base64)){
                path = minioService.putInoutImage(park, base64);
            }
        }catch (Exception e){
            log.error("Do Minio exception", e);
        }

        for (VehicleTrackDTO dto : data.getData()){
            doEnterCompany(park, dto);
            saveInOutEnterprise(park, path, dto);
        }

    }

    public void doGate(String park, VehicleTrackDTO trackDTO){
        PoleVertex newPole = NezhaDeviceManager.getInstance().getPoleVertexByGateNo(park, trackDTO.getCno());
        if (null == newPole){
            log.error("handler gate>>   nezha pole is null, [park: {}, objId {},  cno: {}]", park,  trackDTO.getOid(), trackDTO.getCno());
            return;
        }
        trackDTO.setGtc(trackDTO.getCno());
        String cno = NezhaDeviceManager.getInstance().getCameraCodeByPoleCode(park, newPole.getPoleCode());
        GatePGEntity entity = NezhaDeviceManager.getInstance().getGatePGEntityByGateNo(park, trackDTO.getCno());
        trackDTO.setCno(cno);
        trackDTO.setDit(entity.getGateType()); //进出闸机 -9 出， 9 进
        doTrack(park, newPole, trackDTO);

    }

    public void doEnterCompany(String park, VehicleTrackDTO trackDTO){
        if (8 == trackDTO.getDit()){
            Set<NezhaClient> set = NezhaSessionManager.getInstance().setClientByParkCode(park);
            if (CollectionUtil.isNotEmpty(set)){
                log.info("handler enter company >> trackDTO {} ", trackDTO);
                VehicleManager.getInstance().removeVehicle(park, trackDTO.getVno());
                CoffeeExecutorManager.getInstance().getSlowExecutor().execute(() -> {
                    try {
                        TimeUnit.SECONDS.sleep(10);
                    } catch (InterruptedException e) {
                        log.error("handler enter company >>  sleep error {}", e.getMessage());
                    }
                    for (NezhaClient client : set){
                        client.sendDeleteMsg(new VehicleDeleteMessage(trackDTO.getOid(), trackDTO.getVno()));
                    }
                });

            }
        }
    }

    private void saveTrack(String park, VehicleTrackDTO trackDTO){
        ParkDeviceBuilder builderData = NezhaDeviceManager.getInstance().getBuilderData();
        Map<String, CameraPGEntity> cameraPGEntityMap = Optional.ofNullable(builderData.getCameraMap()).map(c -> c.get(park)).orElse(null);
        if (null == cameraPGEntityMap){
            log.error("cameraPGEntityMap is null : park {}", park);
        }

        VehicleTrackRecordPGEntity entity = new VehicleTrackRecordPGEntity();
        entity.setParkCode(park);
        entity.setPoleCode(cameraPGEntityMap == null ? null: Optional.ofNullable(cameraPGEntityMap.get(trackDTO.getCno())).map(CameraPGEntity::getPoleCode).orElse(null));
        entity.setCameraCode(trackDTO.getCno());
        entity.setObjectId(trackDTO.getOid());
        entity.setPoleLab("y");
        entity.setVehicleNo(trackDTO.getVno());
        entity.setVelocity(trackDTO.getV());
        entity.setLon(trackDTO.getLon());
        entity.setLat(trackDTO.getLat());
        entity.setVehicleType(trackDTO.getType());
        entity.setDit(trackDTO.getDit());
        entity.setDoType(trackDTO.getDoType());
        entity.setAddTime(LocalDateTime.now());
        entity.setActime(trackDTO.getT());
        nezhaRecordService.saveVehicle(park, entity);

    }
    private void saveInOutEnterprise(String park, String path, VehicleTrackDTO trackDTO){

        ParkDeviceBuilder builderData = NezhaDeviceManager.getInstance().getBuilderData();
        Map<String, CameraPGEntity> cameraPGEntityMap = builderData.getCameraMap().get(park);
        VehicleTrackRecordPGEntity entity = new VehicleTrackRecordPGEntity();
        entity.setParkCode(park);
        entity.setPoleCode(cameraPGEntityMap == null ? null: Optional.ofNullable(cameraPGEntityMap.get(trackDTO.getCno())).map(CameraPGEntity::getPoleCode).orElse(null));
        entity.setCameraCode(trackDTO.getCno());
        entity.setObjectId(trackDTO.getOid());
        entity.setPoleLab("y");
        entity.setVehicleNo(trackDTO.getVno());
        entity.setVelocity(trackDTO.getV());
        entity.setLon(trackDTO.getLon());
        entity.setLat(trackDTO.getLat());
        entity.setVehicleType(trackDTO.getType());
        entity.setDit(trackDTO.getDit());
        entity.setDoType(trackDTO.getDoType());
        entity.setAddTime(LocalDateTime.now());
        entity.setEnterprises(trackDTO.getEn());
        entity.setMinioObj(path);
        entity.setActime(trackDTO.getT());
        nezhaRecordService.saveVehicle(park, entity);

    }

    public void doCameraTrack(String park, VehicleTrackDTO trackDTO){

        //1, 通过next 找下一个杆子  下一个杆子可能为空
        PoleVertex newPole = NezhaDeviceManager.getInstance().getPoleVertexByCameraCode(park, trackDTO.getCno(), trackDTO.getDit());
        if (null == newPole){
            saveTrack(park, trackDTO);
            log.error("handler>>  nezha pole is null, [park: {}, trackDTO: {}]", park, trackDTO);
            return;
        }

        doTrack(park, newPole, trackDTO);

    }

    public void doSwerve(String park, VehicleTrackDTO trackDTO){

        PoleVertex newPole = NezhaDeviceManager.getInstance().getPoleVertexByCameraCode(park, trackDTO.getCno(), -1);
        PoleVertex nextPole = NezhaDeviceManager.getInstance().getNextNodeByDit(park, newPole, trackDTO.getDit());

        if (null == nextPole){
            newPole = NezhaDeviceManager.getInstance().getPoleVertexByCameraCode(park, trackDTO.getCno(), 1);
            nextPole = NezhaDeviceManager.getInstance().getNextNodeByDit(park, newPole, trackDTO.getDit());
        }

        if (null == nextPole){
            log.error("handler>>  doSwerve  nezha pole is null, [park: {}, trackDTO: {}]", park, trackDTO);
            return;
        }

        Set<NezhaClient> clientSet = NezhaSessionManager.getInstance().listClientByCameraCode(park, trackDTO.getCno());
        if (CollectionUtil.isEmpty(clientSet)){
            log.info("handler>> client config camera is null, [park: {}, objId {},  cameraCode: {}]", park, trackDTO.getOid(), trackDTO.getCno());
            saveRecord(park, trackDTO, newPole, null);
            return;
        }

        VehicleBO vehicleBO = VehicleManager.getInstance().getVehicleByDTO(park, trackDTO); // vehicle
        vehicleBO.newSecond(); // 设置此时的
        vehicleBO.setV(trackDTO.getV());

        VehiclePathMessage message = doNextPole(park, vehicleBO, newPole, nextPole, trackDTO);
        if (null != message){
            for (NezhaClient client : clientSet){
                client.sendMessage(message);
            }
        }
        //saveRecode
        saveRecord(park, trackDTO, newPole, nextPole);

    }




    private void doTrack(String park, PoleVertex newPole, VehicleTrackDTO trackDTO){
        //2 前端是否有摄像头
        Set<NezhaClient> clientSet = NezhaSessionManager.getInstance().listClientByCameraCode(park, trackDTO.getCno());
        if (CollectionUtil.isEmpty(clientSet)){
            log.info("handler>> client config camera is null, [park: {}, objId {},  cameraCode: {}]", park, trackDTO.getOid(), trackDTO.getCno());
            saveRecord(park, trackDTO, newPole, null);
            return;
        }

        //出闸机
        if (-9 == trackDTO.getDit()){
            log.info("handler out gate >> trackDTO {} ", trackDTO);
            VehicleBO vBo =  VehicleManager.getInstance().getVehicleByVno(park, trackDTO.getVno());
            if (null != vBo){
                VehicleManager.getInstance().removeVehicle(park, trackDTO.getVno());
                for (NezhaClient client : clientSet){
                    client.sendDeleteMsg(new VehicleDeleteMessage(trackDTO.getOid(), trackDTO.getVno()));
                }
            }
            saveRecord(park, trackDTO, newPole, null);
            return;
        }

        VehicleBO vehicleBO = VehicleManager.getInstance().getVehicleByDTO(park, trackDTO); // vehicle
        vehicleBO.newSecond(); // 设置此时的时间
        vehicleBO.setV(trackDTO.getV());

        PoleVertex nextPole = NezhaDeviceManager.getInstance().getNextByVertex(park, newPole); // null 可能岔路
        if (newPole.equalsPole(nextPole)){
            log.info("handler>> now pole and next pole is same [park: {}, objectId: {}, now CM code:{}, next CM code:{}, pole: {}]", park, trackDTO.getOid(), trackDTO.getCno(), nextPole.getPoleCode(), newPole.getPoleCode());
            nextPole = null;
        }


        VehiclePathMessage message = null == nextPole ? doNotNextPole(park, vehicleBO, newPole, trackDTO) : doNextPole(park, vehicleBO, newPole, nextPole, trackDTO);
        log.info("handler>> vehicle {}", vehicleBO);
        if (null != message){
            for (NezhaClient client : clientSet){
                client.sendMessage(message);
            }
        }
        //saveRecode
        saveRecord(park, trackDTO, newPole, nextPole);
    }




    private VehiclePathMessage doNotNextPole(String parkCode, VehicleBO vehicleBO, PoleVertex newPole, VehicleTrackDTO trackDTO){
        //1, 判断 汽车原来有没有节点
        if (null == vehicleBO.getCurrentPole()){
            log.info("handler>> Not next pole-- Vehicle current pole is null , next is nul, Uncertain direction [park: {}, objectId: {}] ", vehicleBO.getParkCode(), vehicleBO.getObjectId());
            vehicleBO.setCurrentPole(newPole);
            return null;
        }
        //和本节点相同则等待
        if (vehicleBO.getCurrentPole().equalsPole(newPole)){
            log.info("handler>> Not next pole-- Vehicle current pole equals new Pole [park: {}, objectId: {}, current pole: {},  new pole: {}] ",
                    vehicleBO.getParkCode(), vehicleBO.getObjectId(), vehicleBO.getCurrentPole().getPoleCode(), newPole.getPoleCode());
            return null;
        }

        //不一致
        log.info("handler>> Not next pole-- current pole and next pole different  [park: {}, objectId: {},  current pole: {},  new pole: {}] ",
                     vehicleBO.getParkCode(), vehicleBO.getObjectId(), vehicleBO.getCurrentPole().getPoleCode(), newPole.getPoleCode());

        List<PointMessage> pointList = betweenPole(parkCode, vehicleBO, vehicleBO.getCurrentPole(), newPole);
        if (CollectionUtil.isEmpty(pointList)){
            log.info("handler>> Not next pole-- compute point is null  [park: {}, objectId: {},  current pole: {},  new pole: {}] ",
                    vehicleBO.getParkCode(), vehicleBO.getObjectId(), vehicleBO.getCurrentPole().getPoleCode(), newPole.getPoleCode());
            vehicleBO.setCurrentPole(newPole);
            return null;
        }

        return buildVehiclePathMessage(vehicleBO, pointList, newPole, trackDTO);

    }

    //有下一个节点
    private VehiclePathMessage doNextPole(String parkCode, VehicleBO vehicleBO, PoleVertex newPole, PoleVertex nextPole, VehicleTrackDTO trackDTO){

        List<PointMessage> pointList;
        if (nextPole.equalsPole(vehicleBO.getCurrentPole())){
            log.info("handler>>  Next pole and Vehicle current pole is same  [park: {}, objectId: {}, current pole: {},  newPole: {}, nextPole: {}] ",
                    vehicleBO.getParkCode(), vehicleBO.getObjectId(), vehicleBO.getCurrentPole().getPoleCode(), newPole.getPoleCode(), nextPole.getPoleCode());
            return null;
        }

        //1, 汽车原来有没有节点
        if (null == vehicleBO.getCurrentPole() || vehicleBO.getCurrentPole().equalsPole(newPole)){ //一致
            log.info("handler>>  Next pole-- new Vehicle or same  [park: {}, objectId: {},  newPole: {}, nextPole: {}] ",
                    vehicleBO.getParkCode(), vehicleBO.getObjectId(), newPole.getPoleCode(), nextPole.getPoleCode());
            pointList = betweenPole(parkCode, vehicleBO, newPole, nextPole);

        }else {
            pointList = Optional.ofNullable(betweenPole(parkCode, vehicleBO, vehicleBO.getCurrentPole(), newPole)).orElse(new ArrayList<>()); //当前节点 和新节点 轨迹
            List<PointMessage> secendList = betweenPole(parkCode, vehicleBO, newPole, nextPole);
            if (CollectionUtil.isNotEmpty(secendList)){
                pointList.addAll(secendList);
            }
        }

        if (CollectionUtil.isEmpty(pointList)){
            log.info("handler>> Next pole-- compute point is null  [park: {}, objectId: {} current pole: {},  new pole: {}] ",
                    vehicleBO.getParkCode(), vehicleBO.getObjectId(), vehicleBO.getCurrentPole().getPoleCode(), newPole.getPoleCode());
            vehicleBO.setCurrentPole(newPole);
            return null;
        }

        return buildVehiclePathMessage(vehicleBO, pointList, nextPole, trackDTO);

    }


    //两个节点之间 定位数据
    private List<PointMessage> betweenPole(String parkCode, VehicleBO vehicleBO, PoleVertex startPole, PoleVertex endPole){
        log.info("handler>> between vertex [from: {} ----> to: {} | parkCode {}, objectId: {}]", startPole.getPoleCode(), endPole.getPoleCode(), parkCode, vehicleBO.getObjectId());
        //不一致
        List<PoleVertex> vertexList = NezhaDeviceManager.getInstance().findVertexPaths(parkCode, startPole, endPole);
        if (CollectionUtil.isEmpty(vertexList) || vertexList.size() == 1 || vertexList.size() > 50){
            log.info("handler>>  between vertex-- vertex list is null or size > 5  [park: {}, objectId: {}, current pole: {},  new pole: {}, vertexList size: {}] ",
                    vehicleBO.getParkCode(), vehicleBO.getObjectId(), null != vehicleBO.getCurrentPole() ? vehicleBO.getCurrentPole().getPoleCode(): "is null", endPole.getPoleCode(), CollectionUtil.isEmpty(vertexList) ? "0" : vertexList.size());
            return null;
        }

        List<PoleEdge> edgeList = NezhaDeviceManager.getInstance().findEdgePaths(parkCode, startPole, endPole);
        if (CollectionUtil.isEmpty(edgeList) || edgeList.size() > 50){
            log.info("handler>>  between vertex-- edge list is null or size > 5  [park: {}, objectId: {}, current pole: {},  new pole: {}, edgeList size: {}] ",
                    vehicleBO.getParkCode(), vehicleBO.getObjectId(), vehicleBO.getCurrentPole().getPoleCode(), endPole.getPoleCode(), CollectionUtil.isEmpty(edgeList) ? "0" : edgeList.size());
            return null;
        }

        return computePoint(vertexList, edgeList, vehicleBO);
    }



    private List<PointMessage> computePoint(List<PoleVertex> vertexList, List<PoleEdge> edgeList, VehicleBO vehicleBO){
        log.info("handler>> compute point, [objectId {},  vertex: {},  edge: {}]", vehicleBO.getObjectId(), vertexList.stream().map(PoleVertex::getPoleCode).collect(Collectors.joining(",")), edgeList.stream().map(PoleEdge::toString).collect(Collectors.joining(",")) );

        List<PointMessage> pointList = new ArrayList<>();
        if (CollectionUtil.isEmpty(edgeList)){
            return pointList;
        }
        for (PoleEdge edge : edgeList){
            List<PointBO> pointBOList = edge.getPointList();
            if (CollectionUtil.isNotEmpty(pointBOList)) {
                for (PointBO bo : pointBOList) {
                    pointList.add(bo.buildMessage(vehicleBO.buildTimeTrack(bo.getT(), vehicleBO.getV())));
                }
            }
        }

        return pointList;
    }



    private VehiclePathMessage buildVehiclePathMessage(VehicleBO vehicleBO, List<PointMessage> pointMessages, PoleVertex newPole, VehicleTrackDTO trackDTO){
        vehicleBO.setCurrentPole(newPole);
        VehiclePathMessage message = new VehiclePathMessage();
        message.setVehicleId(vehicleBO.getObjectId());
        message.setVehicleNo(vehicleBO.getVehicleNo());
        message.setType(vehicleBO.getType());
        message.setGate(9 == trackDTO.getDoType() && trackDTO.getDit() == -9 ? 1 : 0); //是否出闸机
        message.setVelocity(trackDTO.getV());
        message.setPoints(pointMessages);
        return message;
    }


    private void saveRecord(String park, VehicleTrackDTO trackDTO, PoleVertex newPole, PoleVertex nextPole){
        VehicleTrackRecordPGEntity entity = new VehicleTrackRecordPGEntity();
        entity.setParkCode(park);
        entity.setPoleCode(newPole.getPoleCode());
        entity.setPoleLab(newPole.getLab());
        entity.setCameraCode(trackDTO.getCno());
        entity.setObjectId(trackDTO.getOid());

        entity.setVehicleNo(trackDTO.getVno());
        entity.setVelocity(trackDTO.getV());
        entity.setLon(trackDTO.getLon());
        entity.setLat(trackDTO.getLat());
        entity.setVehicleType(trackDTO.getType());
        Optional.ofNullable(nextPole).ifPresent(n -> {
            entity.setNextPole(n.getPoleCode());
            entity.setNextPoleLab(n.getLab());
        });
        entity.setDit(trackDTO.getDit());
        entity.setDoType(trackDTO.getDoType());
        entity.setAddTime(LocalDateTime.now());
        entity.setActime(trackDTO.getT());
        if (StrUtil.isNotEmpty(trackDTO.getGtc())){
            entity.setGateCode(trackDTO.getGtc());
        }
        nezhaRecordService.saveVehicle(park, entity);
    }



}
